package fr.aviz.frontend;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.ResourceBundle;

import javafx.beans.property.BooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Scene;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.ScatterChart;
import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.ColorPicker;
import javafx.scene.control.ListView;
import javafx.scene.control.RadioMenuItem;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextField;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.Tooltip;
import javafx.scene.control.cell.CheckBoxListCell;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.util.Callback;
import javafx.util.StringConverter;
import model.City;
import model.ListItem;
import netscape.javascript.JSObject;

import org.controlsfx.control.RangeSlider;

import com.lynden.gmapsfx.GoogleMapView;
import com.lynden.gmapsfx.MapComponentInitializedListener;
import com.lynden.gmapsfx.javascript.event.UIEventHandler;
import com.lynden.gmapsfx.javascript.object.GoogleMap;
import com.lynden.gmapsfx.javascript.object.InfoWindow;
import com.lynden.gmapsfx.javascript.object.InfoWindowOptions;
import com.lynden.gmapsfx.javascript.object.LatLong;
import com.lynden.gmapsfx.javascript.object.MapOptions;
import com.lynden.gmapsfx.javascript.object.MapTypeIdEnum;
import com.lynden.gmapsfx.javascript.object.Marker;
import com.lynden.gmapsfx.javascript.object.MarkerOptions;

import controller.FileManager;
import fr.aviz.frontend.Popup.Type;


public class AppController implements Initializable, MapComponentInitializedListener 
{
	public final boolean dynamicQuery = false;
	
	@FXML private RadioMenuItem rbmEnglish;
	@FXML private RadioMenuItem rbmFrench;
	@FXML private RadioMenuItem rbmSpanish;
	
    @FXML private GoogleMapView mapView;

    @FXML private RangeSlider rngPopulation;
    @FXML private TextField lblMinPopulation;
    @FXML private TextField lblMaxPopulation;
    
    @FXML private ListView<ListItem> lstRegions;
    @FXML private ListView<ListItem> lstDepartments;
    @FXML private ListView<ListItem> lstParties;
    @FXML private ListView<ListItem> lstStatutes;
    
    @FXML private ColorPicker colourPicker;
    
    @FXML private ScatterChart<Number, Number> sc1;
    @FXML private NumberAxis sc1Bottom;
    @FXML private NumberAxis sc1Left;

    @FXML private ScatterChart<Number, Number> sc2;
    @FXML private NumberAxis sc2Bottom;
    @FXML private NumberAxis sc2Left;
    
    @FXML private ScatterChart<Number, Number> sc3;
    @FXML private NumberAxis sc3Bottom;
    @FXML private NumberAxis sc3Left;
    
    @FXML private ScatterChart<Number, Number> sc4;
    @FXML private NumberAxis sc4Bottom;
    @FXML private NumberAxis sc4Left;

    @FXML private ScatterChart<Number, Number> scDetail;
    @FXML private NumberAxis scBottomDetail;
    @FXML private NumberAxis scLeftDetail;
    
    @FXML private TabPane tabs;
    
    private GoogleMap map;
    
    private ArrayList<Marker> markers = new ArrayList<Marker>();
    
    private City chosenCity;
    
    private ArrayList<City> cities;
    private ObservableList<ListItem> regions;
    private ObservableList<ListItem> departments;
    private ObservableList<ListItem> parties;
    private ObservableList<ListItem> statutes;

    private ResourceBundle bundle = null;
    
    @Override
    public void initialize(URL url, ResourceBundle rb) 
    {
    	bundle = rb;
    	
		ToggleGroup groupLanguage = new ToggleGroup();
		
		rbmEnglish.setToggleGroup(groupLanguage);
		rbmSpanish.setToggleGroup(groupLanguage);
		rbmFrench.setToggleGroup(groupLanguage);
		
		rbmFrench.setGraphic(new ImageView(new Image("resources/images/french.png")));
		rbmEnglish.setGraphic(new ImageView(new Image("resources/images/english.png")));
		rbmSpanish.setGraphic(new ImageView(new Image("resources/images/spanish.png")));
		
        mapView.addMapInializedListener(this);
    }    

    public void resetMap()
    {
    	//Set the initial properties of the map.
        MapOptions mapOptions = new MapOptions();
        
        mapOptions.center(new LatLong(46.77541603, 2.429313604))
                .mapType(MapTypeIdEnum.HYBRID)
                .overviewMapControl(false)
                .panControl(true)
                .rotateControl(false)
                .scaleControl(false)
                .streetViewControl(false)
                .zoomControl(true)
                .zoom(6);
                   
        map = mapView.createMap(mapOptions);
    }
    
    @Override
    public void mapInitialized() 
    {
    	resetMap();

        if (dynamicQuery)
        {
//			Uncomment and add Apache Fuseki libraries for a dynamic implementation.
        	
//        	cities = QueryRunner.getCities();
//        	QueryRunner.getAbsentism(cities);
//        	QueryRunner.getExpressed(cities);
//        	
//        	departments = FXCollections.observableArrayList(getItems(QueryRunner.getDepartments()));
//        	parties = FXCollections.observableArrayList(getItems(QueryRunner.getParties()));
//        	regions = FXCollections.observableArrayList(getItems(QueryRunner.getRegions()));
//        	statutes = FXCollections.observableArrayList(getItems(QueryRunner.getStatutes()));
//        	
//        	Collections.sort(departments);
//        	Collections.sort(parties);
//        	Collections.sort(regions);
//        	Collections.sort(statutes);
        }
        else
        {
        	cities = (ArrayList<City>) FileManager.loadFromFile("data/cities.obj");
        	departments = (ObservableList<ListItem>) FileManager.loadFromFile("data/departments.obj");
        	parties = (ObservableList<ListItem>) FileManager.loadFromFile("data/parties.obj");
        	regions = (ObservableList<ListItem>) FileManager.loadFromFile("data/regions.obj");
        	statutes = (ObservableList<ListItem>) FileManager.loadFromFile("data/statutes.obj");
        }
        
    	
        setLists();
       
        long maxPop = Long.MIN_VALUE;
        for (City c : cities)
        	maxPop = Math.max(maxPop, c.getPopulation());

        rngPopulation.setMin(0);
        rngPopulation.setMax(maxPop);
        rngPopulation.setLowValue(rngPopulation.getMin());
        rngPopulation.setHighValue(rngPopulation.getMax());
		lblMinPopulation.setText(Long.toString(Math.round(rngPopulation.getLowValue())));
		lblMaxPopulation.setText(Long.toString(Math.round(rngPopulation.getHighValue())));
        
        rngPopulation.setOnMouseReleased(new EventHandler<Event>() 
        {
			@Override
			public void handle(Event arg0) 
			{
				lblMinPopulation.setText(Long.toString(Math.round(rngPopulation.getLowValue())));
				lblMaxPopulation.setText(Long.toString(Math.round(rngPopulation.getHighValue())));
				selectCities();
			}
		});

        selectCities();
        
        sc1.setOnMouseClicked(new EventHandler<Event>() 
        {
			@Override
			public void handle(Event arg0) 
			{
				MouseEvent me = (MouseEvent) arg0;

				Number x = sc1Bottom.getValueForDisplay(me.getX()); 
				Number y = sc1Left.getValueForDisplay(me.getY());
				PointInSeries closestPoint = getClosestPoint(x, y, sc1);
				City closestPointCity = (City) closestPoint.getPoint().getExtraValue();

				if (me.getButton() == MouseButton.PRIMARY)
					setColour(closestPointCity);
				else
					copyChart(sc1);
			}
		});

        sc2.setOnMouseClicked(new EventHandler<Event>() 
        {
        	@Override
        	public void handle(Event arg0) 
        	{
        		MouseEvent me = (MouseEvent) arg0;
        		
        		Number x = sc2Bottom.getValueForDisplay(me.getX()); 
        		Number y = sc2Left.getValueForDisplay(me.getY());
        		PointInSeries closestPoint = getClosestPoint(x, y, sc2);		
        		City closestPointCity = (City) closestPoint.getPoint().getExtraValue();
				
				if (me.getButton() == MouseButton.PRIMARY)
					setColour(closestPointCity);
				else
					copyChart(sc2);
        	}
        });

        sc3.setOnMouseClicked(new EventHandler<Event>() 
        {
        	@Override
        	public void handle(Event arg0) 
        	{
        		MouseEvent me = (MouseEvent) arg0;
        		
        		Number x = sc3Bottom.getValueForDisplay(me.getX()); 
        		Number y = sc3Left.getValueForDisplay(me.getY());
        		PointInSeries closestPoint = getClosestPoint(x, y, sc3);
        		City closestPointCity = (City) closestPoint.getPoint().getExtraValue();
				
				if (me.getButton() == MouseButton.PRIMARY)
					setColour(closestPointCity);
				else
					copyChart(sc3);
        	}
        });

        sc4.setOnMouseClicked(new EventHandler<Event>() 
        {
        	@Override
        	public void handle(Event arg0) 
        	{
        		MouseEvent me = (MouseEvent) arg0;
        		
        		Number x = sc4Bottom.getValueForDisplay(me.getX()); 
        		Number y = sc4Left.getValueForDisplay(me.getY());
        		PointInSeries closestPoint = getClosestPoint(x, y, sc4);
        		City closestPointCity = (City) closestPoint.getPoint().getExtraValue();
				
				if (me.getButton() == MouseButton.PRIMARY)
					setColour(closestPointCity);
				else
					copyChart(sc4);
        	}
        });
    }   
    
    public void copyChart(ScatterChart<Number, Number> chart)
    {
		scBottomDetail.setLabel("2001");
		scLeftDetail.setLabel("2008");

    	
		scDetail.getData().clear();

		XYChart.Series series1 = new XYChart.Series();
		series1.setName(chart.getData().get(0).getName());
	
		for (XYChart.Data<Number, Number> p : chart.getData().get(0).getData())
		{
			XYChart.Data<Number, Number> n = new XYChart.Data<Number, Number>();
			n.setXValue(p.getXValue());
			n.setYValue(p.getYValue());
			n.setExtraValue(p.getExtraValue());
			
			series1.getData().add(n);
		}

		scDetail.getData().add(series1);

		scDetail.setOnMouseClicked(new EventHandler<Event>() 
		{
			@Override
			public void handle(Event arg0) 
			{
				MouseEvent me = (MouseEvent) arg0;

				Number x = scBottomDetail.getValueForDisplay(me.getX()); 
				Number y = scLeftDetail.getValueForDisplay(me.getY());
				PointInSeries closestPoint = getClosestPoint(x, y, scDetail);
				City closestPointCity = (City) closestPoint.getPoint().getExtraValue();

				Popup.popupMessage(Type.INFO, closestPointCity.getName(), bundle.getString("city"));
			}
			
		});
		
		tabs.getSelectionModel().select(2);
    }
    
    public PointInSeries getClosestPoint(Number x, Number y, ScatterChart<Number, Number> chart)
    {
    	HashMap<Double, XYChart.Data<Number,Number>> distances = new HashMap<Double, XYChart.Data<Number,Number>>();
    	HashMap<Double, XYChart.Series<Number, Number>> groups = new HashMap<Double, XYChart.Series<Number, Number>>();
    	
		for (XYChart.Series<Number, Number> series : chart.getData())
		{
			for (XYChart.Data<Number,Number> point : series.getData())
			{
				double dist = Math.pow(point.getXValue().doubleValue() - x.doubleValue(), 2);
				dist = dist + Math.pow(point.getYValue().doubleValue() - y.doubleValue(), 2);
				dist = Math.sqrt(dist);

				distances.put(dist, point);
				groups.put(dist, series);
			}
		}
		
		ArrayList<Double> cl = new ArrayList<Double>();
		cl = new ArrayList<Double>(distances.keySet());
		Collections.sort(cl);
		
		Double closestDistance = cl.get(0);
		return new PointInSeries(groups.get(closestDistance), distances.get(closestDistance));
    }
    
    public void addCity(City c)
    {
    	double lon = c.getLon();
    	double lat = c.getLat();
    	String name = c.getName();
    	
    	c.setInChart(true);
    	
        //Add markers to the map
        LatLong position = new LatLong(lon, lat);
        
        MarkerOptions markerOptions = new MarkerOptions();
        markerOptions.position(position);
        markerOptions.title(name);

        Marker marker = new Marker(markerOptions);
        
        markers.add(marker);
        map.addMarker(marker);
        
        UIEventHandler e = new UIEventHandler() 
        {
 			@Override
			public void handle(JSObject obj) 
			{
				LatLong p = new LatLong((JSObject) obj.getMember("latLng"));
				
				HashMap<Double, City> cm = new HashMap<Double, City>();
				
				for (City c : cities)
				{
					double dist = c.getLatLong().distanceFrom(new LatLong(p.getLongitude(), p.getLatitude()));
					cm.put(dist, c);
				}
				
				ArrayList<Double> cl = new ArrayList<Double>();
				cl = new ArrayList<Double>(cm.keySet());
				Collections.sort(cl);
				
				if (cl.size()>0)
				{
					Double closestDistance = cl.get(0);
					chosenCity = cm.get(closestDistance);

					InfoWindowOptions infoWindowOptions = new InfoWindowOptions();
					infoWindowOptions.content("<h2>" + chosenCity.getName() + "</h2>" +
					                  		  "<b>" + bundle.getString("population") + "</b>:" 
							                  + chosenCity.getPopulation() + "<br>" + 
					                  		  "<b>" + bundle.getString("department") + "</b>:" 
							                  + chosenCity.getDepartment() + "<br>" +
					                  		  "<b>" + bundle.getString("region") + "</b>:" 
							                  + chosenCity.getRegion() + "<br>");

					InfoWindow iw = new InfoWindow(infoWindowOptions);
					iw.open(map, marker);
				}

			}
		};
		map.addUIEventHandler(marker.getVariableName(), com.lynden.gmapsfx.javascript.event.UIEventType.click, e);
    }
    
    public ArrayList<ListItem> getItems(ArrayList<String> list)
    {
    	ArrayList<ListItem> result = new ArrayList<ListItem>();
    	
    	for (String s : list)
    		result.add(new ListItem(s));
    	
    	return result;
    }
    
    public void setLists()
    {
        Callback<ListItem, ObservableValue<Boolean>> getProperty = new Callback<ListItem, ObservableValue<Boolean>>() 
		{
            @Override
            public BooleanProperty call(ListItem i) 
			{
                return i.getIsSelected();
            }
        };

        StringConverter scDepartments = new StringConverter<ListItem>() 
		{
            @Override
            public String toString(ListItem t) 
			{
                return t.getName();
            }
 
            @Override
            public ListItem fromString(String string) 
			{
                for (ListItem i : departments) 
				{
                    if (string.equals(i.getName())) 
                        return i;
                }
				
                return null;
            }
        };
        
        StringConverter scRegions = new StringConverter<ListItem>() 
		{
            @Override
            public String toString(ListItem t) 
			{
                return t.getName();
            }
 
            @Override
            public ListItem fromString(String string) 
			{
                for (ListItem i : regions) 
				{
                    if (string.equals(i.getName())) 
                        return i;
                }
				
                return null;
            }
        };
        
        StringConverter scParties = new StringConverter<ListItem>() 
		{
            @Override
            public String toString(ListItem t) 
			{
                return t.getName();
            }
 
            @Override
            public ListItem fromString(String string) 
			{
                for (ListItem i : parties) 
				{
                    if (string.equals(i.getName())) 
                        return i;
                }
				
                return null;
            }
        };

        StringConverter scStatutes = new StringConverter<ListItem>() 
        		{
        	@Override
        	public String toString(ListItem t) 
        	{
        		return t.getName();
        	}
        	
        	@Override
        	public ListItem fromString(String string) 
        	{
        		for (ListItem i : statutes) 
        		{
        			if (string.equals(i.getName())) 
        				return i;
        		}
        		
        		return null;
        	}
        		};
        
        lstRegions.setCellFactory(CheckBoxListCell.forListView(getProperty, scRegions));
        lstParties.setCellFactory(CheckBoxListCell.forListView(getProperty, scParties));
        lstDepartments.setCellFactory(CheckBoxListCell.forListView(getProperty, scDepartments));
        lstStatutes.setCellFactory(CheckBoxListCell.forListView(getProperty, scStatutes));
        
        lstRegions.setItems(regions);
        lstParties.setItems(parties);
        lstDepartments.setItems(departments);
        lstStatutes.setItems(statutes);
        
        for (ListItem i : regions)
        	i.getIsSelected().addListener(new ChangeListener() 
        	{
				@Override
				public void changed(ObservableValue arg0, Object arg1, Object arg2) 
				{
					selectCities();
				}
			});

        for (ListItem i : departments)
        	i.getIsSelected().addListener(new ChangeListener() 
        	{
        		@Override
        		public void changed(ObservableValue arg0, Object arg1, Object arg2) 
        		{
        			selectCities();
        		}
        	});
        
        for (ListItem i : parties)
        	i.getIsSelected().addListener(new ChangeListener() 
        	{
        		@Override
        		public void changed(ObservableValue arg0, Object arg1, Object arg2) 
        		{
        			selectCities();
        		}
        	});

        for (ListItem i : statutes)
        	i.getIsSelected().addListener(new ChangeListener() 
        	{
        		@Override
        		public void changed(ObservableValue arg0, Object arg1, Object arg2) 
        		{
        			selectCities();
        		}
        	});
    }
    
    public void handleBtnUpdateMap(Event e)
    {
    	selectCities();
    }
    
    public void selectCities()
    {
    	for (Marker m : markers)
    		map.removeMarker(m);
    	
    	markers.clear();
    	
        for (City c : cities)
        {
        	c.setInChart(false);
        	
        	if ( isItemSelected(regions, c.getRegion()) || areNoItemsSelected(regions) )
        		if ( isItemSelected(departments, c.getDepartment()) || areNoItemsSelected(departments) )
        			if ( isItemSelected(parties, c.getParty()) || areNoItemsSelected(parties) )
        				if ( isItemSelected(statutes, c.getStatut()) || areNoItemsSelected(statutes) )
        					if (c.getPopulation()>=((long) rngPopulation.getLowValue()) && 
        					    c.getPopulation()<=((long) rngPopulation.getHighValue()) )
        						addCity(c);
        }
        
        setPlots();
    }
    
    public boolean isItemSelected(ObservableList<ListItem> l, String name)
    {
        for (ListItem i : l) 
		{
            if (name.equals(i.getName())) 
                return i.isSelected();
        }
		
        return false;
    }
    
    public boolean areNoItemsSelected(ObservableList<ListItem> items)
    {
    	for (ListItem i : items)
    		if (i.isSelected())
    			return false;
    	
    	return true;
    }

	@FXML
	protected void handleMnuLanguage(Event e)
	{
		for (Marker m : markers)
			map.removeMarker(m);
		
		markers.clear();
		
		if (rbmSpanish.isSelected())
			bundle = ResourceBundle.getBundle("bundles.bundle", Locale.forLanguageTag("es"));
		else if (rbmFrench.isSelected())
			bundle = ResourceBundle.getBundle("bundles.bundle", Locale.forLanguageTag("fr"));
		else
			bundle = ResourceBundle.getBundle("bundles.bundle", Locale.forLanguageTag("en"));
		
		try 
		{
			BorderPane bp = FXMLLoader.load(getClass().getResource("Application.fxml"), bundle);
			Main.primaryStage.setScene(new Scene(bp, 1024, 768));
			Main.primaryStage.setTitle(bundle.getString("application"));
		} 
		catch (IOException e1) 
		{
			throw new RuntimeException(e1);
		}
	}
	
	@FXML
	protected void handleMnuAbout(Event e)
	{
		String message = bundle.getString("about_message");
		
		Popup.popupMessage(Type.INFO, message, bundle.getString("about"));
	}
	
	@FXML
	protected void handleMnuClose(Event e)
	{
		System.exit(0);
	}
	
	public void setPlots()
	{
		XYChart.Series seriesAbs1 = new XYChart.Series();
		seriesAbs1.setName(bundle.getString("abs_1"));
		
		XYChart.Series seriesAbs2 = new XYChart.Series();
		seriesAbs2.setName(bundle.getString("abs_2"));

		XYChart.Series seriesExp1 = new XYChart.Series();
		seriesExp1.setName(bundle.getString("exp_1"));
		
		XYChart.Series seriesExp2 = new XYChart.Series();
		seriesExp2.setName(bundle.getString("exp_2"));

		for (City c : cities)
		{
			if (c.isInChart() && c.hasBothRates("abs", 1))
			{
				XYChart.Data point = new XYChart.Data(c.getRate("abs", 2001, 1), c.getRate("abs", 2008, 1));
				point.setExtraValue(c);
				seriesAbs1.getData().add(point);
			}
			
			if (c.isInChart() && c.hasBothRates("abs", 2))
			{
				XYChart.Data point = new XYChart.Data(c.getRate("abs", 2001, 2), c.getRate("abs", 2008, 2));
				point.setExtraValue(c);
				seriesAbs2.getData().add(point);
			}			

			if (c.isInChart() && c.hasBothRates("exp", 1))
			{
				XYChart.Data point = new XYChart.Data(c.getRate("exp", 2001, 1), c.getRate("exp", 2008, 1));
				point.setExtraValue(c);
				seriesExp1.getData().add(point);
			}
			
			if (c.isInChart() && c.hasBothRates("exp", 2))
			{
				XYChart.Data point = new XYChart.Data(c.getRate("exp", 2001, 2), c.getRate("exp", 2008, 2));
				point.setExtraValue(c);
				seriesExp2.getData().add(point);
			}			
		}

		sc1.getData().clear();
		sc2.getData().clear();
		sc3.getData().clear();
		sc4.getData().clear();
		
		sc1.getData().addAll(seriesAbs1);
		sc2.getData().addAll(seriesAbs2);
		sc3.getData().addAll(seriesExp1);
		sc4.getData().addAll(seriesExp2);

		sc1Bottom.setTickLength(5);
		sc2Bottom.setTickLength(5);
		sc3Bottom.setTickLength(5);
		sc4Bottom.setTickLength(5);
		sc1Left.setTickLength(5);
		sc2Left.setTickLength(5);
		sc3Left.setTickLength(5);
		sc4Left.setTickLength(5);

		sc1Bottom.setMinorTickLength(2.5);
		sc2Bottom.setMinorTickLength(2.5);
		sc3Bottom.setMinorTickLength(2.5);
		sc4Bottom.setMinorTickLength(2.5);
		sc1Left.setMinorTickLength(2.5);
		sc2Left.setMinorTickLength(2.5);
		sc3Left.setMinorTickLength(2.5);
		sc4Left.setMinorTickLength(2.5);
		
		sc1Bottom.setLowerBound(0);
		sc2Bottom.setLowerBound(0);
		sc3Bottom.setLowerBound(0);
		sc4Bottom.setLowerBound(0);
		sc1Left.setLowerBound(0);
		sc2Left.setLowerBound(0);
		sc3Left.setLowerBound(0);
		sc4Left.setLowerBound(0);
		
		sc1Bottom.setLabel("2001");
		sc2Bottom.setLabel("2001");
		sc3Bottom.setLabel("2001");
		sc4Bottom.setLabel("2001");

		sc1Left.setLabel("2008");
		sc2Left.setLabel("2008");
		sc3Left.setLabel("2008");
		sc4Left.setLabel("2008");

		double upperBottom = Math.max( Math.max(sc1Bottom.getUpperBound(), sc2Bottom.getUpperBound()),
				                       Math.max(sc3Bottom.getUpperBound(), sc4Bottom.getUpperBound()) );
		
		double upperLeft   = Math.max( Math.max(sc1Left.getUpperBound(), sc2Left.getUpperBound()),
				                       Math.max(sc3Left.getUpperBound(), sc4Left.getUpperBound()) );
		
		sc1Bottom.setUpperBound(upperBottom);
		sc2Bottom.setUpperBound(upperBottom);
		sc3Bottom.setUpperBound(upperBottom);
		sc4Bottom.setUpperBound(upperBottom);
		sc1Left.setUpperBound(upperLeft);
		sc2Left.setUpperBound(upperLeft);
		sc3Left.setUpperBound(upperLeft);
		sc4Left.setUpperBound(upperLeft);
	}
	
    private String toRgbString(Color c) 
    {
        return "rgb("     + to255Int(c.getRed())
                    + "," + to255Int(c.getGreen())
                    + "," + to255Int(c.getBlue())  + ")";
    }
    
    private int to255Int(double d) 
    {
        return (int) (d * 255);
    }
    
    private void setColour(City city)
    {
    	setColour(sc1, city);
    	setColour(sc2, city);
    	setColour(sc3, city);
    	setColour(sc4, city);
    }
    
    private void setColour(ScatterChart<Number, Number> chart, City city)
    {
    	for (XYChart.Series<Number, Number> series : chart.getData())
		{
			for (XYChart.Data<Number,Number> point : series.getData())
			{
				City city2 = (City) point.getExtraValue();
				
				if ( city2.equals(city) )
					point.getNode().setStyle("-fx-background-color: " + toRgbString(colourPicker.getValue()) + ";");
			}
		}
    }
}